package options

import (
	"bytes"
	"log"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"

	"gitee.com/daledi/public/mlog"
	"github.com/go-redis/redis"
	"github.com/patrickmn/go-cache"
	"gorm.io/driver/mysql"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var (
	GolbalConf *GlobalOPT
)

//GlobalOPT 全局配置
type GlobalOPT struct {
	// 以下配置项是由应用程序设置
	MapCache *cache.Cache
	Redis    *redis.Client
	DB       *gorm.DB
	DBRO     *gorm.DB
	// sqlite3
	SLDB   *gorm.DB
	ExtDB  map[string]*gorm.DB
	ErrLog *mlog.Logger
	// 以下配置项由配置文件设置
	Prefix                string
	PrefixLen             int `internal:"yes"`
	BaseURL               string
	SessionExpire         int64
	ExtOPT                map[string]string
	SessionSecretKey      string
	SessionSecretKeyBytes []byte
	PWDSecretKey          string
	MaxBodySize           int64
	Bind                  string
	Port                  string
	PidFile               string
	LogDir                string
	LogLevel              string
}

//GOPT 全局配置参数
func GOPT() GlobalOPT {
	// Home := os.Getenv("HOME")
	GlobalOPTs := GlobalOPT{
		Prefix:           "/",
		SessionExpire:    3600,
		SessionSecretKey: "s5N2plaxfEnyg27JrRzWNxVIBwDA7IFWG7Hk2rlAKCNHCwysBqprEDMsXKx8hrbn",
		PWDSecretKey:     "6DFmZQD2D0kbNyzHPfYaZXtceCjdb623offPnKPXjZiqDJaYDWrAhkbTSZEMbt0R",
		MaxBodySize:      int64(10 << 20),
		Bind:             "127.0.0.1",
		Port:             "5000",
		LogDir:           "/tmp",
		LogLevel:         "info",
		ExtDB:            make(map[string]*gorm.DB),
		ExtOPT:           make(map[string]string),
	}
	GlobalOPTs.PrefixLen = len(GlobalOPTs.Prefix)
	GlobalOPTs.SessionSecretKeyBytes = []byte(GlobalOPTs.SessionSecretKey)
	GlobalOPTs.PidFile = GlobalOPTs.LogDir + "/" + os.Args[0] + ".pid"
	GlobalOPTs.ErrLog, _ = mlog.New("stdout", 15)
	return GlobalOPTs
}

func GOPTP() *GlobalOPT {
	gopt := GOPT()
	GolbalConf = &gopt
	return &gopt
}
func ReadConfigP(cfile string) (*GlobalOPT, error) {
	gopt, err := ReadConfig(cfile)
	return &gopt, err
}

//ReadConfig 从配置文件读取配置
func ReadConfig(cfile string) (GlobalOPT, error) {
	gopt := GOPT()
	f, err := os.Open(cfile)
	if err != nil {
		return gopt, err
	}
	defer f.Close()
	buf := bytes.NewBuffer([]byte{})
	if _, err := buf.ReadFrom(f); err != nil {
		return gopt, err
	}
	myref := reflect.ValueOf(&gopt).Elem()
	errorLog := "stdout"
	// envlist := []string{}
	// ips := ipsearch.IPRanges{}
	for {
		rdata, err := buf.ReadBytes(0x0a)
		if err != nil {
			break
		}
		line := bytes.TrimSpace(rdata)
		if line[0] == '#' {
			continue
		}
		i := bytes.IndexByte(line, '=')
		name := string(bytes.TrimSpace(line[:i]))
		value := string(bytes.TrimSpace(line[i+1:]))
		switch name {
		case "Port":
			gopt.Port = value
		case "MemCache":
			confs := strings.Split(value, ",")
			if len(confs) != 2 {
				log.Fatalf("config error: %s\n", value)
			}
			var de, ci int64
			if de, err = strconv.ParseInt(confs[0], 10, 64); err != nil {
				log.Fatalf("%s: config is invalid: %v\n", name, err)
			}
			if ci, err = strconv.ParseInt(confs[1], 10, 64); err != nil {
				log.Fatalf("%s: config is invalid: %v\n", name, err)
			}
			gopt.MapCache = cache.New(
				time.Duration(de)*time.Minute,
				time.Duration(ci)*time.Minute,
			)
		case "MySQL":
			// b := strings.LastIndexByte(value, '/') + 1
			// e := strings.LastIndexByte(value, '?')
			// dbName := value[b:e]
			gopt.DB, err = MysqlConn(value, 10, 1)
			if err != nil {
				log.Fatalf("mysql: %v\n", err)
			}
		case "MySQLRO":
			gopt.DBRO, err = MysqlConn(value, 10, 1)
			if err != nil {
				log.Fatalf("mysql: %v\n", err)
			}
		case "Sqlite":
			gopt.SLDB, err = gorm.Open(sqlite.Open("file:"+value+"?_mutex=full&_journal=WAL&_busy_timeout=10000"), &gorm.Config{SkipDefaultTransaction: true})
			if err != nil {
				log.Fatalf("sqlite: %v\n", err)
			}
			// gopt.SLDBRO, err = gorm.Open(sqlite.Open("file:"+value+"?mode=ro"), &gorm.Config{SkipDefaultTransaction: true})
			// if err != nil {
			// 	log.Fatalf("sqlite: %v\n", err)
			// }
		case "error_log":
			errorLog = value
		case "SessionSecretKey":
			gopt.SessionSecretKey = value
			gopt.SessionSecretKeyBytes = []byte(value)
		default:
			field := myref.FieldByName(name)
			if len(name) > 6 && name[:6] == "MySQL_" {
				// 额外的数据库连接
				key := strings.Split(name, "_")[1]
				gopt.ExtDB[key], err = MysqlConn(value, 10, 1)
				if err != nil {
					log.Fatalf("mysql: %v\n", err)
				}
				continue
			}
			if ftype, ok := myref.Type().FieldByName(name); !ok {
				gopt.ExtOPT[name] = value
			} else if ftype.Tag.Get("internal") != "yes" {
				switch field.Type().String() {
				case "string":
					field.SetString(value)
				case "int", "int64":
					if v, err := strconv.ParseInt(value, 10, 0); err == nil {
						field.SetInt(v)
					}
				}
			}
		}
	}
	gopt.PrefixLen = len(gopt.Prefix)
	// if gopt.LogDir == "" {
	// 	log.Fatalln("没有配置LogDir")
	// }
	level := 7
	dbLogLevel := logger.Error
	switch gopt.LogLevel {
	case "debug":
		level = 15
	case "debugv":
		level = 31
		dbLogLevel = logger.Info
	}
	if errorLog != "stdout" {
		errorLog = gopt.LogDir + "/" + errorLog
	}
	if gopt.ErrLog, err = mlog.New(errorLog, level); err != nil {
		log.Fatalf("create error log failed: %v\n", err)
	}
	gormLogger := logger.New(
		gopt.ErrLog.Log,
		logger.Config{
			SlowThreshold: 200 * time.Millisecond,
			LogLevel:      dbLogLevel,
			Colorful:      false,
		},
	)
	if gopt.DB != nil {
		gopt.DB.Config.Logger = gormLogger
	}
	if gopt.DBRO != nil {
		gopt.DBRO.Config.Logger = gormLogger
	}
	for k := range gopt.ExtDB {
		gopt.ExtDB[k].Config.Logger = gormLogger
	}
	GolbalConf = &gopt
	return gopt, nil
}

func MysqlConn(dsn string, moc, mic int) (*gorm.DB, error) {
	gdb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		DisableAutomaticPing:   true,
		SkipDefaultTransaction: true,
		PrepareStmt:            false,
	})
	if err != nil {
		return nil, err
	}
	if db, err := gdb.DB(); err == nil {
		db.SetConnMaxLifetime(time.Hour * 8)
		db.SetMaxOpenConns(moc)
		db.SetMaxIdleConns(mic)
	}
	return gdb, nil
}
